//
// Created by song on 16-12-12.
//

#ifndef C0COMPILER_MIPSINSTRUCTION_H
#define C0COMPILER_MIPSINSTRUCTION_H

#include <string>
#include <sstream>
#include <vector>
#include <map>

using std::string;
using std::stringstream;
using std::vector;
using std::map;

enum Register{
    RA,
    T0,T1,T2,T3,T4,T5,T6,T7,
    A0,A1,A2,A3,
    V0,V1,
    S0,S1,S2,S3,S4,S5,S6,S7,
    SP,FP,

};

class MIPsIns {
    stringstream a;
    vector<string> codeList;
    map<Register, string> Name;
public:

    MIPsIns(){
        Name[RA] = "$ra";
        Name[T0] = "$t0";
        Name[T1] = "$t1";
        Name[T2] = "$t2";
        Name[T3] = "$t3";
        Name[T4] = "$t4";
        Name[T5] = "$t5";
        Name[T6] = "$t6";
        Name[T7] = "$t7";
        Name[A0] = "$a0";
        Name[A1] = "$a1";
        Name[A2] = "$a2";
        Name[A3] = "$a3";
        Name[V0] = "$v0";
        Name[V1] = "$v1";
        Name[S0] = "$s0";
        Name[S1] = "$s1";
        Name[S2] = "$s2";
        Name[S3] = "$s3";
        Name[S4] = "$s4";
        Name[S5] = "$s5";
        Name[S6] = "$s6";
        Name[S7] = "$s7";
        Name[SP] = "$sp";
        Name[FP] = "$fp";
    }

    void done(){
        codeList.push_back(a.str());
        a.str(string());
        a.clear();
    }

    void define(string key, string value){
        a << ".eqv " << key << ' ' << value;
        done();
    }

    void dataSegmentBegin(){
        a << ".data";
        done();
    }

    void textSegmentBegin(){
        a << ".text";
        done();
    }

    void globalString(string name, string content){
        a << name << ": .asciiz " << content;
        done();
    }

    void globalEmptyArray(string name, int lengthOfBytes){
        a << name << ": .space " << lengthOfBytes;
        done();
    }

    void globalVariable(string name, int value){
        a << name << ": .word " << value;
        done();
    }

    void sub(Register target, Register op0, Register op1) {
        a << "sub " << Name[target] << ", " << Name[op0] << ", " << Name[op1];
        done();
    }

    void subiu(Register target, Register operand, int immediateNum) {
        a << "subiu " << Name[target] << ", " << Name[operand] << ", " << immediateNum;
        done();
    }

    void add(Register target, Register op0, Register op1) {
        a << "add " << Name[target] << ", " << Name[op0] << ", " << Name[op1];
        done();
    }

    void addi(Register target, Register operand, int immediateNum) {
        a << "addi " << Name[target] << ", " << Name[operand] << ", " << immediateNum;
        done();
    }

    void addiu(Register target, Register operand, int immediateNum) {
        a << "addiu " << Name[target] << ", " << Name[operand] << ", " << immediateNum;
        done();
    }

    void mul(Register target, Register op0, Register op1){
        a << "mul " << Name[target] << ", " << Name[op0] << ", " << Name[op1];
        done();
    }

    void div(Register target, Register op0, Register op1){
        a << "div " << Name[target] << ", " << Name[op0] << ", " << Name[op1];
        done();
    }

    void sll(Register target, Register value, int shiftBits) {
        a << "sll " << Name[target] << ", " << Name[value] << ", " << shiftBits;
        done();
    }

    void comment(string content) {
        a << "# " << content;
        done();
    }

    void sw(Register source, Register targetAbsoluteAddress) {
        // save to absolute address, same as offset is 0
        // used when update local(stack) array item
        a << "sw " << Name[source] << ", (" << Name[targetAbsoluteAddress] << ')';
        done();
    }

    void sw(Register source, Register targetBaseAddress, int offset) {
        // useful when update local(stack) variable base on $sp
        a << "sw " << Name[source] << ", " << offset << '(' << Name[targetBaseAddress] << ')';
        done();
    }

    void sw(Register source, string targetGlobalDataLabel, Register offset) {
        // used when update global array item
        a << "sw " << Name[source] << ", " << targetGlobalDataLabel << '(' << Name[offset] << ')';
        done();
    }

    void sw(Register source, string targetGlobalDataLabel) {
        // used when update global variable
        a << "sw " << Name[source] << ", " << targetGlobalDataLabel;
        done();
    }

    void lw(Register target, Register sourceAbsoluteAddress) {
        // load from absolute address, same as offset is 0
        // for local(stack) array item
        a << "lw " << Name[target] << ", (" << Name[sourceAbsoluteAddress] << ')';
        done();
    }

    void lw(Register target, Register sourceBaseAddress, int offset) {
        // for local(stack) variable
        a << "lw " << Name[target] << ", " << offset << '(' << Name[sourceBaseAddress] << ')';
        done();
    }

    void lw(Register target, string sourceGlobalDataLabel, Register offset) {
        // for global array item
        a << "lw " << Name[target] << ", " << sourceGlobalDataLabel << '(' << Name[offset] << ')';
        done();
    }

    void lw(Register target, string sourceGlobalDataLabel) {
        // for global variable
        a << "lw " << Name[target] << ", " << sourceGlobalDataLabel;
        done();
    }

    void li(Register target, string valueMacro) {
        a << "li " << Name[target] << ", " << valueMacro;
        done();
    }

    void li(Register target, int value) {
        a << "li " << Name[target] << ", " << value;
        done();
    }

    void la(Register target, string label) {
        a << "la " << Name[target] << ", " << label;
        done();
    }

    void slt(Register result, Register r0, Register r1) {
        a << "slt " << Name[result] << ", " << Name[r0] << ", " << Name[r1];
        done();
    }

    void sle(Register result, Register r0, Register r1) {
        a << "sle " << Name[result] << ", " << Name[r0] << ", " << Name[r1];
        done();
    }

    void sge(Register result, Register r0, Register r1) {
        a << "sge " << Name[result] << ", " << Name[r0] << ", " << Name[r1];
        done();
    }

    void sgt(Register result, Register r0, Register r1) {
        a << "sgt " << Name[result] << ", " << Name[r0] << ", " << Name[r1];
        done();
    }

    void seq(Register result, Register r0, Register r1) {
        a << "seq " << Name[result] << ", " << Name[r0] << ", " << Name[r1];
        done();
    }

    void sne(Register result, Register r0, Register r1) {
        a << "sne " << Name[result] << ", " << Name[r0] << ", " << Name[r1];
        done();
    }

    void bge(Register r0, Register r1, string label) {
        a << "bge " << Name[r0] << ", " << Name[r1] << ", " << label;
        done();
    }

    void bgt(Register r0, Register r1, string label) {
        a << "bgt " << Name[r0] << ", " << Name[r1] << ", " << label;
        done();
    }

    void ble(Register r0, Register r1, string label){
        a << "ble " << Name[r0] << ", " << Name[r1] << ", " << label;
        done();
    }

    void blt(Register r0, Register r1, string label){
        a << "blt " << Name[r0] << ", " << Name[r1] << ", " << label;
        done();
    }

    void beq(Register r0, Register r1, string label){
        a << "beq " << Name[r0] << ", " << Name[r1] << ", " << label;
        done();
    }

    void bne(Register r0, Register r1, string label){
        a << "bne " << Name[r0] << ", " << Name[r1] << ", " << label;
        done();
    }

    void j(string targetLabel) {
        a << "j " << targetLabel;
        done();
    }

    void jr(Register r) {
        a << "jr " << Name[r];
        done();
    }

    void jal(string functionLabel) {
        a << "jal " << functionLabel;
        done();
    }

    void syscall(){
        a << "syscall";
        done();
    }







    void label(string content){
        a << content << ':';
        done();
    }

    string toString(){
        stringstream tmp;
        for(int i=0;i<codeList.size();i++){
            string s = codeList[i];
            if(s[0]=='.'){ // .data .text .eqv : no indent
                tmp << '\n';
                tmp << s;
            }else if(s[0]=='#'){ // comment
                tmp << "  " << s;
            }else if(s[s.length()-1]==':'){ // label in code text, also no indent
                tmp << '\n';
                tmp << s;
            }else{
                tmp << '\n' << '\t' << s;
            }
        }
        tmp << '\n';
        return tmp.str();
    }



};


#endif //C0COMPILER_MIPSINSTRUCTION_H
